﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Migrator.Exceptions;

namespace Migrator.Util
{
    /// <summary>
    /// Represents a MAC address
    /// </summary>
    [Serializable]
    public class MACAddress
    {
        #region Constructors
        /// <summary>
        /// Creates a new MAC address
        /// </summary>
        /// <param name="address">The string representation of the address</param>
        public MACAddress(string address)
        {
            this.octets = Parse(address);
        }
        #endregion

        #region Public methods

        /// <summary>
        /// Returns the address as a byte[]
        /// </summary>
        /// <returns>An array containing the bytes of the address</returns>
        public byte[] GetAddressBytes()
        {
            return (byte[])this.octets.Clone();
        }

        public override string ToString()
        {
            return String.Join(":", octets.Select(s => String.Format("{0:X2}", s)).ToArray());
        }

        public override bool Equals(object obj)
        {
            // Check for null and compare run-time types.
            if (obj == null || GetType() != obj.GetType()) 
                return false;

            MACAddress other = (MACAddress)obj;
            for (int i = 0; i < octets.Length; i++)
            {
                if (this.octets[i] != other.octets[i])
                    return false;
            }

            return true;
        }

        public override int GetHashCode()
        {
            return this.octets.Aggregate(0, (accum, value) => accum ^= value);
        }

        #endregion

        #region Operators

        public static bool operator ==(MACAddress a, MACAddress b)
        {
            // If both are null, or both are same instance, return true.
            if (System.Object.ReferenceEquals(a, b))
            {
                return true;
            }

            // If one is null, but not both, return false.
            if (((object)a == null) || ((object)b == null))
            {
                return false;
            }

            // Return true if the fields match:
            return a.Equals(b);
        }

        public static bool operator !=(MACAddress a, MACAddress b)
        {
            return !(a == b);
        } 
        #endregion

        #region Private methods
        /// <summary>
        /// Parses an MAC address from its string representation
        /// </summary>
        /// <param name="address">The string representation of the address</param>
        /// <exception cref="InvalidIPv4AddressException">
        /// Thrown when the given address is invalid
        /// </exception>
        /// <returns>A byte array with the 6 octets</returns>
        private byte[] Parse(string address)
        {
            const string pattern = "^" +
                                         @"([0-9a-fA-F]{2}):" +
                                         @"([0-9a-fA-F]{2}):" +
                                         @"([0-9a-fA-F]{2}):" +
                                         @"([0-9a-fA-F]{2}):" +
                                         @"([0-9a-fA-F]{2}):" +
                                         @"([0-9a-fA-F]{2})"  +
                                   "$";

            Match match = Regex.Match(address, pattern);
            if (match.Success)
            {
                byte first = Convert.ToByte(match.Groups[1].Value, 16);
                byte second = Convert.ToByte(match.Groups[2].Value, 16);
                byte third = Convert.ToByte(match.Groups[3].Value, 16);
                byte fourth = Convert.ToByte(match.Groups[4].Value, 16);
                byte fifth = Convert.ToByte(match.Groups[5].Value, 16);
                byte sixth = Convert.ToByte(match.Groups[6].Value, 16);

                return new byte[] { first, second, third, fourth, fifth, sixth };
            }

            throw new InvalidAddressException(address);
        }
        #endregion

        #region Private data
        protected byte[] octets; 
        #endregion
    }
}
